package org.apache.harmony.lang.annotation;  

import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.io.Serializable;  
import java.lang.annotation.AnnotationTypeMismatchException;  
import java.lang.reflect.Array;  
import java.lang.reflect.Method;  
import java.util.Arrays;  
  
/** 
 * This class represents member element of an annotation. 
 * It consists of name and value, supplemented with element 
 * definition information (such as declared type of element). 
 * <br>The value may be one of the following types: 
 * <ul> 
 * <li> boxed primitive 
 * <li> Class 
 * <li> enum constant 
 * <li> annotation (nested) 
 * <li> one-dimensional array of the above 
 * <li> Throwable 
 * </ul> 
 * The last type is specific for this implementation; a Throwable value 
 * means that the error occured during parsing or resolution of corresponding 
 * class-data structures and throwing is delayed until the element 
 * is requested for value. 
 * 
 * @see android.lang.annotation.AnnotationFactory 
 * 
 * @author Alexey V. Varlamov, Serguei S. Zapreyev 
 * @version $Revision$ 
 */  
@SuppressWarnings({"serial"})  
public class AnnotationMember implements Serializable {  
  
    /** 
     * Tag description of a Throwable value type. 
     */  
    protected static final char ERROR = '!';  
  
    /** 
     * Tag description of an array value type. 
     */  
    protected static final char ARRAY = '[';  
  
    /** 
     * Tag description of all value types except arrays and Throwables. 
     */  
    protected static final char OTHER = '*';  
  
//    public static final char INT = 'I';  
//    public static final char CHAR = 'C';  
//    public static final char DOUBLE = 'D';  
//    public static final char FLOAT = 'F';  
//    public static final char BYTE = 'B';  
//    public static final char LONG = 'J';  
//    public static final char SHORT = 'S';  
//    public static final char BOOL = 'Z';  
//    public static final char CLASS = 'c';  
//    public static final char ENUM = 'e';  
//    public static final char ANTN = '@';  
  
    private enum DefaultValues {NO_VALUE}  
  
    /** 
     * Singleton representing missing element value. 
     */  
    protected static final Object NO_VALUE = DefaultValues.NO_VALUE;  
  
    protected final String name;  
    protected final Object value; // a primitive value is wrapped to the corresponding wrapper class  
    protected final char tag;  
    // no sense to serialize definition info as it can be changed arbitrarily  
    protected transient Class<?> elementType;  
    protected transient Method definingMethod;  
  
  
    /** 
     * Creates a new element with specified name and value. 
     * Definition info will be provided later when this 
     * element becomes actual annotation member. 
     * @param name element name, must not be null 
     * @param val element value, should be of addmissible type, 
     * as specified in the description of this class 
     * 
     * @see #setDefinition(AnnotationMember) 
     */  
    public AnnotationMember(String name, Object val) {  
        this.name = name;  
        value = val == null ? NO_VALUE : val;  
        if (value instanceof Throwable) {  
            tag = ERROR;  
        } else if (value.getClass().isArray()) {  
            tag = ARRAY;  
        } else {  
            tag = OTHER;  
        }  
    }  
  
    /** 
     * Creates the completely defined element. 
     * @param name element name, must not be null 
     * @param value element value, should be of addmissible type, 
     * as specified in the description of this class 
     * @param m element-defining method, reflected on the annotation type 
     * @param type declared type of this element 
     * (return type of the defining method) 
     */  
    public AnnotationMember(String name, Object val, Class<?> type, Method m) {  
        this(name, val);  
  
        definingMethod = m;  
  
        if (type == int.class) {  
            elementType = Integer.class;  
        } else if (type == boolean.class) {  
            elementType = Boolean.class;  
        } else if (type == char.class) {  
            elementType = Character.class;  
        } else if (type == float.class) {  
            elementType = Float.class;  
        } else if (type == double.class) {  
            elementType = Double.class;  
        } else if (type == long.class) {  
            elementType = Long.class;  
        } else if (type == short.class) {  
            elementType = Short.class;  
        } else if (type == byte.class) {  
            elementType = Byte.class;  
        } else {  
            elementType = type;  
        }  
    }  
  
    /** 
     * Fills in element's definition info and returns this. 
     */  
    protected AnnotationMember setDefinition(AnnotationMember copy) {  
        definingMethod = copy.definingMethod;  
        elementType = copy.elementType;  
        return this;  
    }  
  
    /** 
     * Returns readable description of this annotation value. 
     */  
    public String toString() {  
        if (tag == ARRAY) {  
            StringBuilder sb = new StringBuilder(80);  
            sb.append(name).append("=[");  
            int len = Array.getLength(value);  
            for (int i = 0; i < len; i++) {  
                if (i != 0) sb.append(", ");  
                sb.append(Array.get(value, i));  
            }  
            return sb.append("]").toString();  
        } else {  
            return name+ "=" +value;  
        }  
    }  
  
    /** 
     * Returns true if the specified object represents equal element 
     * (equivalent name-value pair). 
     * <br> A special case is the contained Throwable value; it is considered 
     * transcendent so no other element would be equal. 
     * @return true if passed object is equivalent element representation, 
     * false otherwise 
     * @see #equalArrayValue(Object) 
     * @see java.lang.annotation.Annotation#equals(Object) 
     */  
    public boolean equals(Object obj) {  
        if (obj == this) {  
            // not a mere optimization,  
            // this is needed for consistency with hashCode()  
            return true;  
        }  
        if (obj instanceof AnnotationMember) {  
            AnnotationMember that = (AnnotationMember)obj;  
            if (name.equals(that.name) && tag == that.tag) {  
                if (tag == ARRAY) {  
                    return equalArrayValue(that.value);  
                } else if (tag == ERROR) {  
                    // undefined value is incomparable (transcendent)  
                    return false;  
                } else {  
                    return value.equals(that.value);  
                }  
            }  
        }  
        return false;  
    }  
  
    /** 
     * Returns true if the contained value and a passed object are equal arrays, 
     * false otherwise. Appropriate overloaded method of Arrays.equals() 
     * is used for equality testing. 
     * @see java.util.Arrays#equals(java.lang.Object[], java.lang.Object[]) 
     * @return true if the value is array and is equal to specified object, 
     * false otherwise 
     */  
    public boolean equalArrayValue(Object otherValue) {  
        if (value instanceof Object[] && otherValue instanceof Object[]) {  
            return Arrays.equals((Object[])value, (Object[])otherValue);  
        }  
        Class<?> type = value.getClass();  
        if (type != otherValue.getClass()) {  
            return false;  
        }  
        if (type == int[].class) {  
            return Arrays.equals((int[])value, (int[])otherValue);  
        } else if (type == byte[].class) {  
            return Arrays.equals((byte[])value, (byte[])otherValue);  
        } else if (type == short[].class) {  
            return Arrays.equals((short[])value, (short[])otherValue);  
        } else if (type == long[].class) {  
            return Arrays.equals((long[])value, (long[])otherValue);  
        } else if (type == char[].class) {  
            return Arrays.equals((char[])value, (char[])otherValue);  
        } else if (type == boolean[].class) {  
            return Arrays.equals((boolean[])value, (boolean[])otherValue);  
        } else if (type == float[].class) {  
            return Arrays.equals((float[])value, (float[])otherValue);  
        } else if (type == double[].class) {  
            return Arrays.equals((double[])value, (double[])otherValue);  
        }  
        return false;  
    }  
  
    /** 
     * Computes hash code of this element. The formula is as follows: 
     * <code> (name.hashCode() * 127) ^ value.hashCode() </code> 
     * <br>If value is an array, one of overloaded Arrays.hashCode() 
     * methods is used. 
     * @return the hash code 
     * @see java.util.Arrays#hashCode(java.lang.Object[]) 
     * @see java.lang.annotation.Annotation#hashCode() 
     */  
    public int hashCode() {  
        int hash = name.hashCode() * 127;  
        if (tag == ARRAY) {  
            Class<?> type = value.getClass();  
            if (type == int[].class) {  
                return hash ^ Arrays.hashCode((int[])value);  
            } else if (type == byte[].class) {  
                return hash ^ Arrays.hashCode((byte[])value);  
            } else if (type == short[].class) {  
                return hash ^ Arrays.hashCode((short[])value);  
            } else if (type == long[].class) {  
                return hash ^ Arrays.hashCode((long[])value);  
            } else if (type == char[].class) {  
                return hash ^ Arrays.hashCode((char[])value);  
            } else if (type == boolean[].class) {  
                return hash ^ Arrays.hashCode((boolean[])value);  
            } else if (type == float[].class) {  
                return hash ^ Arrays.hashCode((float[])value);  
            } else if (type == double[].class) {  
                return hash ^ Arrays.hashCode((double[])value);  
            }  
            return hash ^ Arrays.hashCode((Object[])value);  
        } else {  
            return hash ^ value.hashCode();  
        }  
    }  
  
    /** 
     * Throws contained error (if any) with a renewed stack trace. 
     */  
    public void rethrowError() throws Throwable {  
        if (tag == ERROR) {  
            // need to throw cloned exception for thread safety  
            // besides it is better to provide actual stack trace  
            // rather than recorded during parsing  
  
            // first check for expected types  
            if (value instanceof TypeNotPresentException) {  
                TypeNotPresentException tnpe = (TypeNotPresentException)value;  
                throw new TypeNotPresentException(tnpe.typeName(), tnpe.getCause());  
            } else if (value instanceof EnumConstantNotPresentException) {  
                EnumConstantNotPresentException ecnpe = (EnumConstantNotPresentException)value;  
                throw new EnumConstantNotPresentException(ecnpe.enumType(), ecnpe.constantName());  
            } else if (value instanceof ArrayStoreException) {  
                ArrayStoreException ase = (ArrayStoreException)value;  
                throw new ArrayStoreException(ase.getMessage());  
            }  
            // got some other error, have to go with deep cloning  
            // via serialization mechanism  
            Throwable error = (Throwable)value;  
            StackTraceElement[] ste = error.getStackTrace();  
            ByteArrayOutputStream bos = new ByteArrayOutputStream(  
                    ste == null ? 512 : (ste.length + 1) * 80);  
            ObjectOutputStream oos = new ObjectOutputStream(bos);  
            oos.writeObject(error);  
            oos.flush();  
            oos.close();  
            ByteArrayInputStream bis = new ByteArrayInputStream(bos  
                    .toByteArray());  
            ObjectInputStream ois = new ObjectInputStream(bis);  
            error = (Throwable)ois.readObject();  
            ois.close();  
  
            throw error;  
        }  
    }  
  
    /** 
     * Validates contained value against its member definition 
     * and if ok returns the value. 
     * Otherwise, if the value type mismatches definition 
     * or the value itself describes an error, 
     * throws appropriate exception. 
     * <br> Note, this method may return null if this element was constructed 
     * with such value. 
     * 
     * @see #rethrowError() 
     * @see #copyValue() 
     * @return actual valid value or null if no value 
     */  
    public Object validateValue() throws Throwable {  
        if (tag == ERROR) {  
            rethrowError();  
        }  
        if (value == NO_VALUE) {  
            return null;  
        }  
        if (elementType == value.getClass()  
                || elementType.isInstance(value)) { // nested annotation value  
            return copyValue();  
        } else {  
            throw new AnnotationTypeMismatchException(definingMethod,  
                    value.getClass().getName());  
        }  
  
    }  
  
  
    /** 
     * Provides mutation-safe access to contained value. That is, caller is free 
     * to modify the returned value, it will not affect the contained data value. 
     * @return cloned value if it is mutable or the original immutable value 
     */  
    public Object copyValue() throws Throwable  
    {  
        if (tag != ARRAY || Array.getLength(value) == 0) {  
            return value;  
        }  
        Class<?> type = value.getClass();  
        if (type == int[].class) {  
            return ((int[])value).clone();  
        } else if (type == byte[].class) {  
            return ((byte[])value).clone();  
        } else if (type == short[].class) {  
            return ((short[])value).clone();  
        } else if (type == long[].class) {  
            return ((long[])value).clone();  
        } else if (type == char[].class) {  
            return ((char[])value).clone();  
        } else if (type == boolean[].class) {  
            return ((boolean[])value).clone();  
        } else if (type == float[].class) {  
            return ((float[])value).clone();  
        } else if (type == double[].class) {  
            return ((double[])value).clone();  
        }  
        return ((Object[])value).clone();  
    }  
}  

